#define _GNU_SOURCE
#include "net.h"

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

static int set_nonblock(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1)
        return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

/******************************************************************************
 * net_listen_sock - Create and listen server socket
 ******************************************************************************/
int net_listen_sock(const char *addr, int port)
{
    int listen_fd;
    const int optval = 1;
    struct sockaddr_in server_addr;

    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation failed");
        return -1;
    }

    // Set SO_REUSEADDR and SO_REUSEPORT
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        perror("setsockopt SO_REUSEADDR failed");
        close(listen_fd);
        return -1;
    }

    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0) {
        perror("setsockopt SO_REUSEPORT failed");
        close(listen_fd);
        return -1;
    }

    // bind addr and port
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(addr);
    server_addr.sin_port = htons(port);

    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(listen_fd);
        return -1;
    }

    if (listen(listen_fd, SOMAXCONN) < 0) {
        perror("listen failed");
        close(listen_fd);
        return -1;
    }

    if (set_nonblock(listen_fd) < 0) {
        perror("set_nonbloc failed");
        close(listen_fd);
        return -1;
    }

    return listen_fd;
}

/******************************************************************************
 * net_accept_sock - Accept client connection
 * @server_sock: server socket server_sock
 * @handle_accept: on new connection callback
 ******************************************************************************/
int net_accept_sock(int server_sock, void (*handle_accept)(int, void *), void *data)
{
    int client_sock, ret = -1;
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);

    while (1) {
        client_sock = accept(server_sock, (struct sockaddr *)&addr, &addrlen);
        if (client_sock >= 0) {
            printf("- accept\n");
            // got new connection
            set_nonblock(client_sock);
            handle_accept(client_sock, data);
            printf("accepted connection from %s:%d\n", inet_ntoa(addr.sin_addr),
                   ntohs(addr.sin_port));
        } else {
            printf("- errno: %d\n", errno);
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // no pending connections, all handled
                ret = 0;
                break;
            } else if (errno == EINTR) {
                // system call interrupted, try again
                continue;
            } else if (errno == ECONNABORTED) {
                // connection has been aborted, handle next
                continue;
            } else {
                perror("accept failed");
                ret = -1;
                break;
            }
        }
    }

    return ret;
}

/******************************************************************************
 * new_connect_sock - As a client connect to server
 *******************************************************************************/
int new_connect_sock()
{
    return 0;
}
